﻿using MiniFox.Components;
using MiniFox.Data.EntityFramework;
using MiniFox.Infrastructure;
using MiniFox.Platform;
using Repository.Core;
using Repository.Core.Models;
using Repository.Core.Models.Dto;
using Repository.Core.Models.Queries;
using Repository.Core.Models.VO;
using Repository.Core.Providers;
using Repository.Data.Entities.MySql;
using System.Xml.Linq;

namespace Repository.Data
{
    [AutoResolve]
    public class VersionProvider : EntityDataProvider<RepositoryContext>, IVersionProvider
    {
        private long GetPublishedVersionId(string repoName, string versionSetName)
        {
            var pubVersionId = (from p in this.CurrentDbContext.PublishLog
                            join v in this.CurrentDbContext.VersionSet on p.Id equals v.PubId
                            where v.RepoName == repoName && v.Name == versionSetName
                            select p.VersionId).SingleOrDefault();
            return pubVersionId;
        }

        public ResultObject<IEnumerable<VersionSetVo>> Query(string repoName, PagedQueryObject<CommonQuery> request)
        {
            var query = from vi in this.CurrentDbContext.VersionItem
                        join v in this.CurrentDbContext.VersionSet on new { vi.RepoName, vi.VersionSetName } equals new { v.RepoName, VersionSetName = v.Name }
                        join log in this.CurrentDbContext.PublishLog on v.PubId equals log.Id into pl
                        from p in pl.DefaultIfEmpty()
                        where v.RepoName == repoName
                        select new
                        {
                            v.Name,
                            v.RepoName,
                            v.Description,
                            v.UpdateTime,
                            PublishTime = p == null ? null : p.PublishTime as DateTime?,
                            PubVersionId = p == null ? null : p.VersionId as long?,

                            vi.Id,
                            vi.Operation,
                            vi.VersionTime,
                            vi.AuthorName,
                            vi.AuthorId,
                            vi.Comment
                        };
            var groups = (from v in query 
                          group v by new
                          {
                              v.Name,
                              v.RepoName,
                              v.Description,
                              v.UpdateTime,
                              v.PublishTime,
                              v.PubVersionId
                          } into g 
                          select new
                          {
                              g.Key.Name,
                              g.Key.Description,
                              g.Key.UpdateTime,
                              g.Key.PubVersionId,
                              g.Key.PublishTime,
                              LastVersionTime = g.Max(vi => vi.VersionTime),
                              Count = g.Count()
                          });
            var result = this.PagedQueryExecute(groups, request);
            ResultObject<IEnumerable<VersionSetVo>> response = result.GetResultObject(v => new VersionSetVo
            {
                Name = v.Name,
                Description = v.Description,
                UpdateTime = v.UpdateTime,
                LastVersionTime = v.LastVersionTime,
                PubVersionId = v.PubVersionId,
                PublishTime = v.PublishTime,
                Count = v.Count
            });
            return response;
        }

        public ResultObject<IEnumerable<VersionItemVo>> VersionItems(string repoName, string versionSetName, PagedQueryObject<VersionQuery> request)
        {
            var pubVersionId = GetPublishedVersionId(repoName, versionSetName);
            var query = (from v in this.CurrentDbContext.VersionItem
                         where v.RepoName == repoName && v.VersionSetName == versionSetName
                         select new
                         {
                             VersionId = v.Id,
                             v.RepoName,
                             v.VersionSetName,
                             v.VersionTime,
                             v.AuthorId,
                             v.AuthorName,
                             v.Operation,
                             v.Comment,
                             Published = pubVersionId == v.Id
                         });
            var result = this.PagedQueryExecute(query, request);
            ResultObject<IEnumerable<VersionItemVo>> response = result.GetResultObject(item => new VersionItemVo
            {
                VersionID = item.VersionId,
                VersionSetName = item.VersionSetName,
                VersionTime = item.VersionTime,
                AuthorID = item.AuthorId,
                AuthorName = item.AuthorName,
                OperationType = Enum.Parse<VersionOperationType>(item.Operation, true),
                Comment = item.Comment,
                Published = item.Published
            });
            return response;
        }

        public VersionItemVo GetItem(string repoName, string versionSetName, long? versionId)
        {
            var pubVersionId = GetPublishedVersionId(repoName, versionSetName);
            var item = this.CurrentDbContext.VersionItem.SingleOrDefault(v => v.RepoName == repoName && v.VersionSetName == versionSetName && v.Id == versionId);
            if (item == null) return null;
            return new VersionItemVo
            {
                VersionID = item.Id,
                VersionSetName = item.VersionSetName,
                VersionTime = item.VersionTime,
                AuthorID = item.AuthorId,
                AuthorName = item.AuthorName,
                OperationType = Enum.Parse<VersionOperationType>(item.Operation, true),
                Comment = item.Comment,
                Published = item.Id == pubVersionId
            };
        }

        public string GetItemContent(string repoName, string versionSetName, long versionId)
        {
            var version = this.CurrentDbContext.VersionItem.SingleOrDefault(v => v.RepoName == repoName && v.VersionSetName == versionSetName && v.Id == versionId);
            return version.Content;
        }

        public bool CheckOut(string repoName, SystemIdentity user, string versionSetName)
        {
            var item = this.CurrentDbContext.WorkItem.SingleOrDefault(c => c.RepoName == repoName && c.Name == versionSetName && c.OwnerId == user.GlobalID);
            if (item == null)
            {
                return this.Pull(user, repoName, versionSetName, null, true);
            }
            item.Submitted = false;
            return this.Update(item) > 0;
        }

        public bool Commit(string repoName, SystemIdentity user, CommitDto dto)
        {
            string name = dto.Name;
            string comment = dto.Comment;
            var item = this.CurrentDbContext.WorkItem.SingleOrDefault(w => w.RepoName == repoName && w.Name == name && w.OwnerId == user.GlobalID);
            item.Submitted = true;
            if (this.Update(item) > 0)
            {
                return CreateVersion(item, comment);
            }
            return false;
        }

        private bool NewVersion(WorkItem workItem, VersionItem old_version)
        {
            if (old_version == null) return true;
            if (workItem.Content != old_version.Content) return true;
            return false;
        }
        private bool CreateVersion(WorkItem workItem, string comment)
        {
            if (!workItem.Submitted)
                return true;
            var old_version = (from v in this.CurrentDbContext.VersionItem
                               where v.RepoName == workItem.RepoName && v.VersionSetName == workItem.Name
                               orderby v.VersionTime descending
                               select v).FirstOrDefault();
            if (!NewVersion(workItem, old_version))
                return true;

            VersionSet versionSet = this.CurrentDbContext.VersionSet.SingleOrDefault(v => v.RepoName == workItem.RepoName && v.Name == workItem.Name);
            var newSet = versionSet == null;
            if (newSet)
            {
                versionSet = new VersionSet();
                versionSet.Name = workItem.Name;
                versionSet.RepoName = workItem.RepoName;
            }
            versionSet.Description = workItem.Description;
            versionSet.UpdateTime = DateTime.Now;
            bool success;
            if (newSet)
            {
                success = this.Insert(versionSet) > 0;
            }
            else
            {
                success = this.Update(versionSet) > 0;
            }
            if (!success)
                return false;

            VersionItem new_version = new()
            {
                RepoName = workItem.RepoName,
                VersionSetName = workItem.Name,
                AuthorId = workItem.OwnerId,
                AuthorName = workItem.OwnerName,
                Operation = (old_version == null ? VersionOperationType.新增 : VersionOperationType.修改).ToString(),
                VersionTime = DateTime.Now,
                Content = workItem.Content,
                Comment = comment
            };

            return this.Insert(new_version) > 0;
        }

        protected bool Pull(SystemIdentity user, string repoName, string name, long? versionId, bool checkout)
        {
            var versionSet = this.CurrentDbContext.VersionSet.SingleOrDefault(v => v.RepoName == repoName && v.Name == name);
            var versionItems = from v in this.CurrentDbContext.VersionItem
                                  where v.RepoName == repoName && v.VersionSetName == name
                                  orderby v.VersionTime descending
                                  select v;

            VersionItem version;
            if (versionId == null)
            {
                version = versionItems.FirstOrDefault();
            }
            else
            {
                version = versionItems.SingleOrDefault(v => v.Id == versionId);
            }
            var item = this.CurrentDbContext.WorkItem.SingleOrDefault(c => c.Name == name && c.OwnerId == user.GlobalID);
            bool newItem = item == null;
            if (newItem)
            {
                item = new WorkItem
                {
                    Name = name,
                    OwnerId = user.GlobalID,
                    OwnerName = user.CombineUserName(),
                    UpdateTime = DateTime.Now
                };
            }
            else if (!item.Submitted)//签出状态下忽略版本代码
            {
                return true;
            }

            item.Submitted = !checkout;//获取并签出
            item.UpdateTime = DateTime.Now;

            item.Description = versionSet.Description;
            item.Content = version.Content;
            bool success;
            if (newItem)
                success = this.Insert(item) > 0;
            else
                success = this.Update(item) > 0;

            return success;
        }

        public bool Pull(string repoName, SystemIdentity user, string versionSetName, long? versionId)
        {
            return this.Pull(user, repoName, versionSetName, versionId, false);
        }

        public bool Sync(string repoName, SystemIdentity user)
        {
            var names = this.CurrentDbContext.VersionItem.Where(v => v.RepoName == repoName).Select(v => v.VersionSetName).Distinct().ToArray();
            bool success = true;
            foreach (string name in names)
            {
                success &= this.Pull(repoName, user, name, null);
            }
            return success;
        }
    }
}
